package check2

import (
	"fmt"
	"strconv"
	"strings"

	"gitee.com/u-language/u-language/ucom/ast"
	"gitee.com/u-language/u-language/ucom/enum"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/internal/errutil"
	"gitee.com/u-language/u-language/ucom/internal/utils"
	"gitee.com/u-language/u-language/ucom/ir2"
)

func CheckFile(f *ir2.File) {
	llen := len(f.VarInit)
	for i := 0; i < llen; i++ { //检查变量初始化
		checkIr(f, &f.VarInit[i], f.VarInit, &i)
	}
	llen = len(f.Ir)
	for i := 0; i < llen; i++ { //检查指令
		checkIr(f, &f.Ir[i], f.Ir, &i)
	}
}

// checkIr 检查一条ir指令
func checkIr(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) (typ string) {
	switch v.Op {
	case ir2.VarOP, ir2.TmpVarOP, ir2.RbraceOP, ir2.EndCallOp, ir2.SemicolonOp, ir2.CommaOp, ir2.ObjOP, ir2.LbraceOp, ir2.LineFeedOp, ir2.RPARENOp: //无需检查
	case ir2.ADDOP, ir2.SUBOP, ir2.MULOP, ir2.DIVOP, ir2.LessOp, ir2.GreaterOp, ir2.EqualOp, ir2.NoEqualOp, ir2.RemainOp: //运算
		typ = checkOp(f, v, ir, i)
	case ir2.MOVOP: //赋值
		checkMov(f, v, ir, i)
	case ir2.CallOP: //调用
		checkCall(f, v, ir, i)
	case ir2.FuncOP:
		f.FuncInfo = (**v.Func()).(*ast.FuncNode).FuncInfo
	case ir2.RetOp: //返回
		checkRet(f, v, ir, i)
	case ir2.PaPaOp: //传递参数
		checkPaPa(f, v, ir, i)
	case ir2.LocalVarOp: //TODO:支持检查局部变量
	case ir2.ForOp: //TODO:支持检查for
	case ir2.IfOp: //TODO:支持检查if
	case ir2.ElseIfOp: //TODO:支持检查else if
	case ir2.ElseOp: //TODO:支持检查else
	case ir2.TypeConvertOp: //TODO:支持检查类型转换
	default:
		panic(errutil.NewErr2(errutil.CompileErr, fmt.Sprintf("未知的ir：%+v", *v)))
	}
	return typ
}

// checkOp 检查一个运算
func checkOp(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) string {
	decl := &ir[*i-1]
	if isTmpVar(v.ResultObj) { //如果结果保存到临时变量
		if decl.Op != ir2.TmpVarOP {
			panic(errutil.NewErr2(errutil.CompileErr, "没发现临时变量"))
		}
		decl.Arg2Obj = v.Arg1Typ //设置临时变量类型
	}
	if !utils.Typ_is_equal(v.Arg1Typ, v.Arg2Typ) { //如果不是相同类型
		f.Errctx.Panic(f.FileName, v.LineNum, errcode.NewMsgOpexprTypeIsNotEqual(v.Arg1Typ, v.Arg2Typ), errcode.TypeIsNotEqual)
		return ""
	}
	switch v.Op {
	case ir2.EqualOp, ir2.NoEqualOp, ir2.LessOp, ir2.GreaterOp: //如果是大于小于等于不等运算
		return enum.Bool
	}
	return v.Arg1Typ
}

// checkMov 检查一个赋值
func checkMov(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) {
	if v.Arg2Obj != enum.StrFalse { //如果包含源操作数
		if v.Arg1Typ != v.ResultTyp {
			f.Errctx.Panic(f.FileName, v.LineNum, nil, errcode.TypeIsNotEqual)
			return
		}
	}
	//TODO:支持不包含源操作数的类型检查
	return
}

// isTmpVar 判断是不是临时变量
func isTmpVar(name string) bool {
	return strings.HasPrefix(name, enum.Tmp)
}

// checkCall 检查一个调用
func checkCall(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) {
	ok := predefined_func(f, v, i, ir)
	if ok { //如果是预定义的函数或视为函数调用的类型转换
		return
	}
	info := f.Sbt.Have(v.Arg1Obj)
	if info.Kind != enum.SymbolFunc && info.Kind != enum.SymbolMethod { //如果调用非函数
		f.Errctx.Panic(f.FileName, v.LineNum, errcode.NewMsgSymbol((**v.FuncName()).FuncName()), errcode.CallNoFunc)
	}
	if v.Arg2Obj == enum.StrFalse { //如果没有传参
		return
	}
	*i++
	for ; *i < len(ir); *i++ { //检查所有传参
		if ir[*i].Op != ir2.PaPaOp {
			checkIr(f, &ir[*i], ir, i)
		}
	}
}

// checkRet 检查一个返回
func checkRet(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) {
	if v.Arg1Obj == "" { //如果没有返回值
		if len(f.FuncInfo.RetValue) == 0 { //如果没有声明返回值
			return
		}
		if v.Arg2Obj != enum.StrFalse { //如果返回值在后面
			end, err := strconv.Atoi(v.ResultObj)
			if err != nil {
				panic(errutil.NewErr2(errutil.CompileErr, err.Error()))
			}
			for *i < end-1 { //检查返回值
				*i++
				checkIr(f, &ir[*i], ir, i)
			}
			*i++
			typ := checkIr(f, &ir[*i], ir, i) //获取返回值类型
			if typ == "" {
				panic(errutil.NewErr2(errutil.CompileErr, fmt.Sprintf("找不到返回值类型")))
			}
			declType := f.FuncInfo.RetValue[0].Type.Typ()
			if !utils.Typ_is_equal(typ, declType) { //如果类型不相等
				f.Errctx.Panic(f.FileName, v.LineNum, errcode.NewMsgReturnTypeIsNotEqual(declType, typ), errcode.ReturnErr)
			}
			return
		}
		f.Errctx.Panic(f.FileName, v.LineNum, nil, errcode.RetWithValueInFuncWithoutValue)
		return
	}
	tmp := v.Arg1Typ
	declType := f.FuncInfo.RetValue[0].Type.Typ()
	if !utils.Typ_is_equal(tmp, declType) { //如果类型不相等
		f.Errctx.Panic(f.FileName, v.LineNum, errcode.NewMsgReturnTypeIsNotEqual(declType, tmp), errcode.ReturnErr)
	}
}

// checkPaPa 检查一个传参
func checkPaPa(f *ir2.File, v *ir2.IrNode, ir []ir2.IrNode, i *int) {
	n, err := strconv.Atoi(v.Arg1Obj)
	if err != nil {
		panic(errutil.NewErr2(errutil.CompileErr, err.Error()))
	}
	decl := f.FuncInfo.Parame[n]
	tmp := findTmpTyp(v.Arg2Obj, i, ir)
	decl_typ := decl.Type.Typ()
	if !utils.Typ_is_equal(tmp, decl_typ) { //如果传递参数类型与声明参数类型不相等
		f.Errctx.Panic(f.FileName, v.LineNum, errcode.NewMsgCallTypeIsNotEqual(decl_typ, tmp, n), errcode.TypeIsNotEqual)
	}
}

// findTmpTyp 获取临时变量的类型
func findTmpTyp(s string, i *int, ir []ir2.IrNode) string {
	tmp := ""
	//获取临时变量的类型
	for index := *i; index >= 0; index-- {
		if ir[index].Op == ir2.TmpVarOP && ir[index].Arg1Obj == s { //如果是临时变量的声明，其中有类型
			tmp = ir[index].Arg2Obj
			break
		}
	}
	if tmp == "" {
		panic(errutil.NewErr2(errutil.CompileErr, fmt.Sprintf("未找到临时变量 %s 类型", s)))
	}
	return tmp
}
